/*
	#########################################################
	#			Verzhak										#
	#	e-mail - <verzhak@gmail.com>						#
	#########################################################
	#	Version - 0.0.2										#
	#	Date - 01.03.2009									#
	#	License - GPLv3										#
	#########################################################
*/

/* bashorg.c - основные функции модуля
 * Модуль создает файл /dev/bashorg, с помощью которого можно получить rss-ленту bash.org.ru
 * !!! Важно! /dev/bashorg изначально доступен на чтение для root и группы root.
 * !!! Для исправления ситуации настройте udev
 */ 

// Наш заголовочный файл, в нем - функционал по получению и парсингу rss-ленты
#include "get_rss.h"

// module.h, kernel.h, init.h - каждый модуль использует эти файлы 
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
// Используем vmalloc, поскольку неизвестно, насколько длинна rss-лента (~60 кб. (я беру с запасом - ~120 кб.))
#include <linux/vmalloc.h>
// fs.h и miscdevice.h используются для манипуляций с /dev/bashorg
#include <linux/fs.h>
#include <linux/miscdevice.h>

// Название модуля
#define MY_MODULE_NAME "bashorg"
// Название файла в /dev (соотв. /dev/bashorg)
#define RSS_FILE_DEV "bashorg"

// Здесь будет хранится лента между вызовами fun_read_rss_dev

struct rss_data_t
{
	char *value;
} rss_data;

/* fun_read_rss_dev вызывается в том случае, если было произведено обращение к /dev/bashorg на чтение
 * Аргументы:
 * 	file - описатель /dev/bashorg'а (не нужно)
 * 	buf - буфер для данных (м.б. меньше нашей ленты)
 * 	count - размер буфера
 * 	ppos - нам не нужно :-)
 * Возвращает - количество записанных в буфер байт
 *
 * fun_read_rss_dev вызывается до тех пор, пока не будет возвращен 0 (признак конца файла)
 * 
 * Статегия:
 * 	cat /dev/bashorg
 * 	функция вызывается в первый раз (flag == 0)
 * 		получаем rss ленту
 * 		записываем ленту в rss_data.value
 * 		записываем в буфер первые символы (пока буфер не будет заполнен)
 * 		устанавливаем признак "полученной rss ленты" (flag = 1)
 * 		возвращаем количество записанных символов
 * 	функция вызывается снова, flag == 1
 * 		записываем в буфер следующие count символы
 * 		возвращаем count
 * 	функция вызывается снова, но count > количество_оставшихся символов и flag == 1
 * 		записываем в буфер последние символы
 * 		устанавливаем признак (flag == 2) конца файла
 * 		возвращаем количество записанных символов
 * 	функция вызывается снова, flag == 2
 * 		сбрасываем все статические переменные в начальные значения (по существу, лишь "flag = 0" важно)
 * 		возвращаем 0
 * 	операция чтения /dev/bashorg прекращается
 * 	на экран выводится rss лента
 */

static ssize_t fun_read_rss_dev(struct file * file, char * buf,size_t count, loff_t *ppos)
{
	/* Флаг текущего вызова функции:
	 * 	0 - первый вызов (нужно получить ленту)
	 * 	1 - следующий вызов (возвращаем следующий блок символов)
	 * 	2 - последний вызов (возвращаем признак конца ленты)
	 */
	static int flag = 0;
	// Длина ленты
	static size_t len = 0;
	// Смещение в rss_data.value - индекс первого символа в следующем блоке символов 
	static unsigned int next = 0;
	unsigned int x;
	if (flag == 0)
	{
		// Получаем ленту
		char *val = get_rss();
		strcpy(rss_data.value,val);
		len = strlen(rss_data.value);
		// Освобождаем буфер (память в val выделяется в get_rss())
		vfree(val);
		flag = 1;
	}
	else if (flag == 2 || next >= SIZE_REZ)
	{
		// Лента кончилася или кончился rss_data.value (т.е. лента слишком большая)
		flag = 0;
		len = 0;
		next = 0;
		return 0;
	}
	// Пишем в буфер следующий блок
	// Да, да. Я мог бы использовать strcpy :-)
	for(x=0;x<count;x++)
	{
		buf[x] = rss_data.value[next + x];
		if(rss_data.value[next + x] == '\0')
		{
			// Достигли конца ленты
			flag = 2;
			break;
		}
	}
	next += x;
	return x;
}

/* Описание операции с /dev/bashorg
 * owner - имя модуля, ответственного за файл
 * read - callback-функция для операции "чтение"
 */
static const struct file_operations rss_dev_fops =
{
	.owner = THIS_MODULE,
	.read = fun_read_rss_dev
};

// Описание /dev/bashorg'а
static struct miscdevice rss_dev =
{
	MISC_DYNAMIC_MINOR,
 	RSS_FILE_DEV,
	&rss_dev_fops
};

/* Инициализация модуля (! есть в каждом модуле)
 * Возвращает - 0 в случае успеха, 1 в случае неудачи
 */
static int __init my_init(void)
{
	// Создаем /dev/bashorg
	if(misc_register(&rss_dev))
	{
		//printk - сообщение выводится в /var/log/message (dmesg в консоли)
		printk("Error\n");
		return 1;
	}
	// Выделяем память под буфер для rss ленты
	// (да, да sizeof(char) - глупо, однако это привычка :-) )
	rss_data.value = vmalloc(sizeof(char) * SIZE_REZ);
	return 0;
}

// Функция деинициализации модуля (вызывается при выгрузке модуля) (! есть в каждом модуле)
static void __exit my_exit(void)
{
	// Удаляем /dev/bashorg
	misc_deregister(&rss_dev);
	// Удаляем буфер
	vfree(rss_data.value);
}

// Регистрируем функцию инициализации и деинициализации
module_init(my_init);
module_exit(my_exit);

// Кое-какая информация
MODULE_LICENSE("GPL");
MODULE_AUTHOR("verzhak verzhak@gmail.com AND radist-elvin.blogspot.com");
MODULE_DESCRIPTION("bash.org.ru RSS reader in Linux Kernel");
MODULE_VERSION("0.0.1 - 05.08.2008");
